package me.itsoo.elasticbatis.util;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import me.itsoo.elasticbatis.parsing.druid.sql.SQLUtils;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.SQLStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.expr.SQLBinaryOpExpr;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLDeleteStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLInsertStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLJoinTableSource;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLSelectQueryBlock;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLSelectStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.ast.statement.SQLUpdateStatement;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import me.itsoo.elasticbatis.parsing.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import me.itsoo.elasticbatis.parsing.druid.sql.parser.SQLStatementParser;

import java.util.List;

/**
 * SqlAnalyzer
 *
 * @author zhangxy4
 * @version 0.0.1
 * @since 2025/4/2
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SqlAnalyzer {
    private static final String SELECT = "SELECT";
    private static final String INSERT = "INSERT";
    private static final String UPDATE = "UPDATE";
    private static final String DELETE = "DELETE";

    private static final String UNKNOWN = "UNKNOWN";

    private static final String DOC_ID = "_id";

    /**
     * 是否根据doc_id进行新增或修改
     *
     * @param sql SQL
     * @return boolean
     */
    public static ActionType getActionType(String sql) {
        SQLStatementParser parser = new MySqlStatementParser(sql);
        List<SQLStatement> stmtList = parser.parseStatementList();
        if (stmtList.size() != 1) {
            return ActionType.UNSUPPORT;
        }

        String sqlType = getSqlType(stmtList.get(0));
        if (INSERT.equals(sqlType)) {
            return ActionType.INSERT_OR_UPDATE_BY_PK;
        }

        boolean isSingleTable = isSingleTableOperation(stmtList.get(0));
        boolean isBasedOnId = isBasedOnId(stmtList.get(0));
        if (!isSingleTable || !isBasedOnId) {
            return ActionType.UNSUPPORT;
        }

        if (UPDATE.equals(sqlType)) {
            return ActionType.INSERT_OR_UPDATE_BY_PK;
        } else if (DELETE.equals(sqlType)) {
            return ActionType.DELETE_BY_PK;
        } else if (SELECT.equals(sqlType)) {
            return ActionType.SELECT_BY_PK;
        }
        return ActionType.UNSUPPORT;
    }

    private static String getSqlType(SQLStatement stmt) {
        if (stmt instanceof SQLSelectStatement) {
            return SELECT;
        } else if (stmt instanceof SQLInsertStatement) {
            return INSERT;
        } else if (stmt instanceof SQLUpdateStatement) {
            return UPDATE;
        } else if (stmt instanceof SQLDeleteStatement) {
            return DELETE;
        }
        return UNKNOWN;
    }

    private static boolean isSingleTableOperation(SQLStatement stmt) {
        if (stmt instanceof SQLSelectStatement) {
            SQLSelectQueryBlock qb = ((SQLSelectStatement) stmt).getSelect().getQueryBlock();
            return !(qb.getFrom() instanceof SQLJoinTableSource);
        }
        return (stmt instanceof SQLInsertStatement)
                || (stmt instanceof SQLUpdateStatement)
                || (stmt instanceof SQLDeleteStatement);
    }

    private static boolean isBasedOnId(SQLStatement stmt) {
        final boolean[] hasPrimaryKey = {false};
        final int[] conditionCounts = {0};
        stmt.accept(new MySqlASTVisitorAdapter() {
            @Override
            public boolean visit(SQLBinaryOpExpr x) {
                ++conditionCounts[0];
                if (DOC_ID.equalsIgnoreCase(SQLUtils.toMySqlString(x.getLeft()))) {
                    hasPrimaryKey[0] = true;
                }
                return super.visit(x);
            }
        });
        return (conditionCounts[0] == 1) && hasPrimaryKey[0];
    }


    /**
     * 解析SQL的结果：主要区分操作类型，以及是否为单表、主键操作
     */
    public enum ActionType {
        /**
         * All types
         */
        SELECT_BY_PK,
        INSERT_OR_UPDATE_BY_PK,
        DELETE_BY_PK,
        UNSUPPORT,
    }
}
